import { App, Config, Inject, Middleware } from '@midwayjs/core';
import * as _ from 'lodash';
import { CoolCommException, CoolUrlTagData, TagTypes } from '@cool-midway/core';
import * as jwt from 'jsonwebtoken';
import { NextFunction, Context } from '@midwayjs/koa';
import {
  IMiddleware,
  IMidwayApplication,
  Init,
  InjectClient,
} from '@midwayjs/core';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
import { Utils } from '../../../comm/utils';

/**
 * 权限校验中间件
 * - 仅对以 /admin/ 开头的接口进行鉴权与鉴权放行判断
 * - 支持基于 Tag 的忽略路径（如登录、验证码等无需 token）
 * - 校验 JWT、单点登录(SSO)、密码版本、接口权限点
 * - 超级管理员(admin)在非刷新 token 情况下拥有全部权限
 */
@Middleware()
export class BaseAuthorityMiddleware
  implements IMiddleware<Context, NextFunction>
{
  @Config('koa.globalPrefix')
  prefix;

  @Config('module.base')
  jwtConfig;

  @InjectClient(CachingFactory, 'default')
  midwayCache: MidwayCache;

  @Inject()
  coolUrlTagData: CoolUrlTagData;

  @App()
  app: IMidwayApplication;

  @Inject()
  utils: Utils;

  // 需要忽略鉴权的 URL 列表（支持通配匹配）
  ignoreUrls: string[] = [];

  @Init()
  async init() {
    // 从 URL 标签数据中读取被标记为忽略 token 的 admin 端路由
    this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'admin');
  }

  resolve() {
    return async (ctx: Context, next: NextFunction) => {
      // 默认 200 表示通过，>200 则中断并抛出异常
      let statusCode = 200;
      let { url } = ctx;
      // 去除全局前缀并移除 query，仅保留纯路径
      url = url.replace(this.prefix, '').split('?')[0];
      // 读取请求头中的 Authorization 作为 token
      const token = ctx.get('Authorization');
      const adminUrl = '/admin/';
      // 路由地址为 admin前缀的 需要权限校验
      if (_.startsWith(url, adminUrl)) {
        try {
          // 校验 JWT，若校验成功则将解码后的信息挂载在 ctx.admin
          ctx.admin = jwt.verify(token, this.jwtConfig.jwt.secret);
          // 刷新 token 不允许直接访问业务接口
          if (ctx.admin.isRefresh) {
            ctx.status = 401;
            throw new CoolCommException('登录失效~', ctx.status);
          }
        } catch (error) {}
        // 忽略路径判断：支持通配/正则匹配
        const isIgnored = this.ignoreUrls.some(pattern =>
          this.utils.matchUrl(pattern, url)
        );
        if (isIgnored) {
          // 命中忽略规则，直接放行
          await next();
          return;
        }
        if (ctx.admin) {
          // 读取缓存中的有效访问 token（用于 SSO 校验）
          const rToken = await this.midwayCache.get(
            `admin:token:${ctx.admin.userId}`
          );
          // 判断密码版本是否正确（修改密码后，旧 token 全部失效）
          const passwordV = await this.midwayCache.get(
            `admin:passwordVersion:${ctx.admin.userId}`
          );
          if (passwordV != ctx.admin.passwordVersion) {
            throw new CoolCommException('登录失效~', 401);
          }
          // 超管拥有所有权限
          if (ctx.admin.username == 'admin' && !ctx.admin.isRefresh) {
            // 开启 SSO 时，必须与缓存中的当前 token 一致
            if (rToken !== token && this.jwtConfig.jwt.sso) {
              throw new CoolCommException('登录失效~', 401);
            } else {
              await next();
              return;
            }
          }
          // 要登录每个人都有权限的接口
          if (
            new RegExp(`^${adminUrl}?.*/comm/`).test(url) ||
            // 字典接口
            url == '/admin/dict/info/data'
          ) {
            // 登录后公共接口直接放行
            await next();
            return;
          }
          // 如果传的token是refreshToken则校验失败
          if (ctx.admin.isRefresh) {
            throw new CoolCommException('登录失效~', 401);
          }
          if (!rToken) {
            // 未获取到缓存 token，判定为未登录或登录失效
            throw new CoolCommException('登录失效或无权限访问~', 401);
          }
          if (rToken !== token && this.jwtConfig.jwt.sso) {
            // SSO 模式下多地登录或 token 不一致，返回 401
            statusCode = 401;
          } else {
            // 获取权限点集合，并将冒号权限点转换为 URL 风格的斜杠
            let perms: string[] = await this.midwayCache.get(
              `admin:perms:${ctx.admin.userId}`
            );
            if (!_.isEmpty(perms)) {
              perms = perms.map(e => {
                return e.replace(/:/g, '/');
              });
              // 权限点不包含当前路径则 403 无权限
              if (!perms.includes(url.split('?')[0].replace('/admin/', ''))) {
                statusCode = 403;
              }
            } else {
              // 无任何权限点记录，默认无权限
              statusCode = 403;
            }
          }
        } else {
          // 未登录
          statusCode = 401;
        }
        if (statusCode > 200) {
          // 统一抛出异常由上层处理
          throw new CoolCommException('登录失效或无权限访问~', statusCode);
        }
      }
      // 非 /admin/ 前缀或鉴权通过，继续后续中间件/控制器
      await next();
    };
  }
}
